package it.tukano.swingdeco.containers;

import it.tukano.collections.ListExt;
import it.tukano.fun.Function1;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.Insets;
import java.awt.LayoutManager;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.JPanel;
import javax.swing.border.Border;
import javax.swing.event.AncestorEvent;
import javax.swing.event.AncestorListener;

public class JPanelExt extends JPanel {

    private ListExt<Runnable> onAddListeners = ListExt.<Runnable>newArrayListExt();
    private static final long serialVersionUID = 1;
    private boolean checkMinimumWidth;
    private boolean checkMinimumHeight;
    private Component displayedComponent;
    private Image bgImage;

    public JPanelExt() {
    }
    
    public JPanelExt(Number rowCount, Number colCount) {
        this(new GridLayout(rowCount.intValue(), colCount.intValue()));
    }

    public JPanelExt(LayoutManager layout) {
        super(layout);
    }
    
    public JPanelExt opaque(boolean val) {
        setOpaque(val);
        return this;
    }
    
    public JPanelExt withSize(int w, int h) {
        setSize(w, h);
        return this;
    }
    
    public JPanelExt useBorder(Border b) {
        setBorder(b);
        return this;
    }

    public JPanelExt withName(String name) {
        setName(name);
        return this;
    }

    public JPanelExt withPreferredSize(int x, int y) {
        super.setPreferredSize(new Dimension(x, y));
        return this;
    }

    public JPanelExt withMinimumSize(int x, int y) {
        super.setMinimumSize(new Dimension(x, y));
        return this;
    }

    public JPanelExt checkMinimumWidth() {
        this.checkMinimumWidth = true;
        return this;
    }

    public JPanelExt checkMinimumHeight() {
        this.checkMinimumHeight = true;
        return this;
    }
    
    public <T extends Component> T get(Class<T> type, int index) {
        return
                index < getComponentCount() && type.isAssignableFrom(getComponent(index).getClass()) ?
                type.cast(getComponent(index)) :
                null;
    }
    
    public <T extends Component> T get(Class<T> type, String name) {
        for (int i = 0; i < getComponents().length; i++) {
            Component component = getComponents()[i];
            if(name.equals(component.getName()) && type.isAssignableFrom(component.getClass())) return type.cast(component);
        }
        return null;
    }
    
    public<T extends Component>  JPanelExt  with(Class<T> type, String name, Function1<T, ?> call) {
        T component = this.<T>get(type, name);
        if(component != null) call.apply(component);
        return this;
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        if(bgImage != null) {
            Insets ins = getInsets();
            if(getBorder() != null && getBorder().isBorderOpaque()) {
                g.drawImage(bgImage, ins.left, ins.top, null);
            } else {
                g.drawImage(bgImage, 0, 0, null);
            }
        }
    }

    @Override
    public Dimension getPreferredSize() {
        Dimension size = super.getPreferredSize();
        Dimension min = super.getMinimumSize();
        if(checkMinimumWidth) {
            size.width = Math.max(size.width, min.width);
        }
        if(checkMinimumHeight) {
            size.height = Math.max(size.height, min.height);
        }
        return size;
    }

    public JPanelExt appendAll(Iterable<? extends Component> components) {
        for (Component component : components) {
            add(component);
        }
        return this;
    }

    public JPanelExt appendWithTitles(Object... titleComponentArray) {
        for (int i = 0; i < titleComponentArray.length; i+=2) {
            String title = (String) titleComponentArray[i];
            Component component = (Component) titleComponentArray[i + 1];
            JPanel panel = new JPanel(new GridLayout(1, 1));
            panel.setBorder(BorderFactory.createTitledBorder(title)) ;
            panel.add(component);
            add(panel);
        }
        return this;
    }
    
    public JPanelExt addComponent(Object constraints, Component component) {
        add(component, constraints);
        return this;
    }

    public JPanelExt append(Object... componentConstraintArray) {
        for (int i = 0; i < componentConstraintArray.length; i+=2) {
            Component component = (Component) componentConstraintArray[i];
            Object constraints = componentConstraintArray[i+1];
            add(component, constraints);
        }
        return this;
    }

    public JPanelExt append(Component... c) {
        for (int i = 0; i < c.length; i++) {
            Component component = c[i];
            add(component);
        }
        return this;
    }

    public JPanelExt add(Component... c) {
        for (int i = 0; i < c.length; i++) {
            Component component = c[i];
            add(component);
        }
        return this;
    }

    /**
     * Add some empty components
     * @param count how many empty components to add
     * @return this
     */
    public JPanelExt appendEmpty(int count) {
        for (int i= 0; i < count; i++) {
            append(Box.createGlue());
        }
        return this;
    }

    /**
     * Applies a titled border to this component
     * @param title the title to apply
     * @return this
     */
    public JPanelExt setTitledBorder(String title) {
        setBorder(BorderFactory.createTitledBorder(title));
        return this;
    }

    @Override
    public void addNotify() {
        super.addNotify();
        for (Runnable runnable : onAddListeners) {
            runnable.run();
        }
    }

    /**
     * Set the visibility of this component to false
     * @return this
     */
    public JPanelExt setHidden() {
        setVisible(false);
        return this;
    }

    /**
     * Set the visibility of this component to true
     * @return this
     */
    public JPanelExt setVisible() {
        setVisible(true);
        return this;
    }

    /**
     * Executes some code when the component is removed from the visualization tree
     * @param task the task to execute
     * @return this
     */
    public JPanelExt onRemove(final Runnable task) {
        addAncestorListener(new AncestorListener() {

            public void ancestorAdded(AncestorEvent event) {
            }

            public void ancestorRemoved(AncestorEvent event) {
                if(task != null) task.run();
            }

            public void ancestorMoved(AncestorEvent event) {
            }
        });
        return this;
    }

    public JPanelExt onAdd(Runnable task) {
        onAddListeners.add(task);
        return this;
    }

    /**
     * Removes all components and add the given one. The component is added in
     * the default location of the layout manager of this container (ie center for
     * a BorderLayout). If the component is null, removes everything.
     * @param component the component to display, can be null
     */
    public JPanelExt setComponent(Component component) {
        this.displayedComponent = component;
        removeAll();
        if(component != null) add(component);
        revalidate();
        repaint();
        return this;
    }

    public Component getComponent() {
        return displayedComponent;
    }

    public void useBackgroundImage(Image bgImage) {
        this.bgImage = bgImage;
    }
}
